<?php
class ContainerNumberValidator {
    private   $__alphabetNumerical = array(
        'A' => 10, 'B' => 12, 'C' => 13, 'D' => 14, 'E' => 15, 'F' => 16,
        'G' => 17, 'H' => 18, 'I' => 19, 'J' => 20, 'K' => 21, 'L' => 23,
        'M' => 24, 'N' => 25, 'O' => 26, 'P' => 27, 'Q' => 28, 'R' => 29,
        'S' => 30, 'T' => 31, 'U' => 32, 'V' => 34, 'W' => 35, 'X' => 36,
        'Y' => 37, 'Z' => 38
    );
    protected $_pattern = '/^([A-Z]{3})(U|J|Z)(\d{6})(\d)$/';
    protected $_patternWithoutCheckDigit = '/^([A-Z]{3})(U|J|Z)(\d{6})$/';
    protected $_errorMessages = array();
    protected $_ownerCode = array();
    protected $_productGroupCode;
    protected $_registrationDigit = array();
    protected $_checkDigit;
    
    function isValid( $containerNo ) {
        $valid = $this->validate( $containerNo );
        
        if ( empty($this->_errorMessages) ) {
            return true;
        }
        return false;
    }
    
    function validate( $containerNo ) {
        $matches = array();
        if ( !empty($containerNo) && is_string($containerNo) ) {
            $matches = $this->_identify( $containerNo );
            
            if ( count($matches) !== 5 ) {
                $this->_errorMessages[] = 'Invalid container number';
            } else {
                $checkDigit = $this->_createCheckDigit( $matches );
                
                if ( $this->_checkDigit != $checkDigit ) {
                    $this->_errorMessages[] = 'Check digit is not match';
                    $matches = array();
                }
            }
        } else {
            $this->_errorMessages = array('Container number must be a string');
        }
        return $matches;
    }
    
    function getErrorMessages() {
        return $this->_errorMessages;
    }
    
    function getOwnerCode() {
        if ( empty($this->_ownerCode) ) {
            $this->_errorMessages[] = 'You must call validate or isValid first';
        }
        return $this->_ownerCode;
    }
    
    function getProductGroupCode() {
        if ( empty($this->_productGroupCode) ) {
            $this->_errorMessages[] = 'You must call validate or isValid first';
        }
        return $this->_productGroupCode;
    }
    
    function getRegistrationDigit() {
        if ( empty($this->_registrationDigit) ) {
            $this->_errorMessages[] = 'You must call validate or isValid first';
        }
        return $this->_registrationDigit;
    }
    
    function getCheckDigit() {
        if ( empty($this->_checkDigit) ) {
            $this->_errorMessages[] = 'You must call validate or isValid first';
        }
        return $this->_checkDigit;
    }
    
    function generate( $ownerCode, $productGroupCode, $from = 0, $to = 999999) {
        $alphabetCode = strtoupper($ownerCode . $productGroupCode);
        $containers_no = array();
        
        if ( is_string($alphabetCode) && strlen($ownerCode) === 3 && strlen($productGroupCode) === 1 ) {
            $containers_no = array();
            $current_container_no = '';
            $current_container_check_digit = '';
            
            if ( ($from >= 0) && ($to < 1000000) && (($to - $from) > 0) ) {
                for ( $i = $from; $i <= $to; $i++) {
                    $current_container_no = $alphabetCode . str_pad($i, 6, '0', STR_PAD_LEFT);
                    $current_container_check_digit = $this->createCheckDigit( $current_container_no );
                    
                    if ( $current_container_check_digit < 0 ) {
                        $this->_errorMessages[] = 'Error generating container number at number ' . $i;
                        return $containers_no;
                    }
                    
                    $containers_no[$i] = $current_container_no . $current_container_check_digit;
                }
            } else {
                $this->_errorMessages[] = 'Invalid number to generate, minimal is 0 and maximal is 999999';
            }
            
        } else {
            $this->_errorMessages[] = 'Invalid owner code or product group code';
        }
        
        return $containers_no;
    }
    
    function createCheckDigit( $containerNo ) {
        $checkDigit = -1;
        if ( !empty($containerNo) && is_string($containerNo) ) {
            $matches = $this->_identify( $containerNo, true );
            
            if ( count($matches) !== 4 ) {
                $this->_errorMessages[] = 'Invalid container number';
            } else {
                $checkDigit = $this->_createCheckDigit( $matches );
                if ( $checkDigit < 0 ) {
                    $this->_errorMessages[] = 'Invalid container number';
                }
            }
        } else {
            $this->_errorMessages[] = 'Container number must be a string';
        }
        return $checkDigit;
    }
    
    function clearErrors() {
        $this->_errorMessages = array();
    }
    
    protected function _createCheckDigit( $matches ) {
        $this->_ownerCode = str_split($matches[1]);
        $this->_productGroupCode = $matches[2];
        $this->_registrationDigit = str_split($matches[3]);
        $this->_checkDigit = $matches[4];
        
        // convert owner code + product group code to its numerical value
        $numericalOwnerCode = array();
        for ( $i = 0; $i < count($this->_ownerCode); $i++ ) {
            $numericalOwnerCode[$i] = $this->__alphabetNumerical[$this->_ownerCode[$i]];
        }
        $numericalOwnerCode[] = $this->__alphabetNumerical[$this->_productGroupCode];
        
        // merge numerical owner code with registration digit
        $numericalCode = array_merge( $numericalOwnerCode, $this->_registrationDigit );
        $sumDigit = 0;
        
        // check six-digit registration number and last check digit
        for ( $i = 0; $i < count($numericalCode); $i++ ) {
            $sumDigit += $numericalCode[$i] * pow(2, $i);
        }
        
        $sumDigitDiff = floor($sumDigit / 11) * 11;
        $checkDigit = $sumDigit - $sumDigitDiff;
        return ($checkDigit == 10) ? 0 : $checkDigit;
    }
    
    protected function _identify( $containerNo, $withoutCheckDigit = false ) {
        $this->clearErrors();
        if ( $withoutCheckDigit ) {
            preg_match( $this->_patternWithoutCheckDigit, strtoupper($containerNo), $matches);
        } else {
            preg_match( $this->_pattern, strtoupper($containerNo), $matches);
        }
        return $matches;
    }
}
?>